using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

namespace TaskFramework
{
    public class TaskDispatcher : ITaskDispatcher
    {
        private const string TIME_FORMAT = "{0:HH:mm:ss.fff}";

        private readonly ITaskPool _taskPool;
        private readonly Dictionary<Type, ITaskFactory> _taskFactories = new Dictionary<Type, ITaskFactory>();
        private bool _writeDebugInfo;

        public TaskDispatcher(ITaskPool taskPool)
        {
            _taskPool = taskPool;
            _taskPool.CreateTask = CreateTask;
        }

        private void _taskPool_RootTaskDead(int rootTaskId)
        {
            List<int> affectedBatches = new List<int>();

            foreach (KeyValuePair<int, BatchScopeInfo> batch in _batchedTasks)
            {
                if (batch.Value.OutstandingTaskIds.Contains(rootTaskId))
                {
                    affectedBatches.Add(batch.Key);
                }
            }

            foreach (int batchId in affectedBatches)
            {
                _batchedTasks[batchId].OutstandingTaskIds.Remove(batchId);
                if (_batchedTasks[batchId].OutstandingTaskIds.Count == 0)
                {
                    try
                    {
                        _batchedTasks[batchId].Callback.Invoke(new BatchEvent(batchId));
                    }
                    catch (Exception) { }
                    
                }
            }
        }

        private void _taskPool_TaskCompleted(TaskEvent e)
        {
            if (e.TaskCompleted || e.Exception != null)
            {
                DoBatchHandling(e);
            }

            FireTaskCompletedEvent(e);
        }

        private void DoBatchHandling(TaskEvent e)
        {
            List<int> batchesToAddTo = new List<int>();

            foreach (KeyValuePair<int, BatchScopeInfo> task in _batchedTasks)
            {
                foreach (int taskId in task.Value.OutstandingTaskIds)
                {
                    if (taskId == e.Info.Task.TaskId)
                    {
                        batchesToAddTo.Add(task.Key);
                    }
                }
            }

            for (int i = 0; i < batchesToAddTo.Count; i++)
            {
                _batchedTasks[batchesToAddTo[i]].OutstandingTaskIds.AddRange(e.Info.RoutedTaskIds);
            }

            List<int> tasksToRemove = new List<int>();

            foreach (KeyValuePair<int, BatchScopeInfo> task in _batchedTasks)
            {
                if (task.Value.OutstandingTaskIds.Contains(e.Info.Task.TaskId))
                {
                    tasksToRemove.Add(task.Key); // add the batch id
                }
            }

            for (int i = 0; i < tasksToRemove.Count; i++)
            {
                _batchedTasks[tasksToRemove[i]].OutstandingTaskIds.Remove(e.Info.Task.TaskId);

                if (_batchedTasks[tasksToRemove[i]].OutstandingTaskIds.Count == 0)
                {
                    try
                    {
                        _batchedTasks[tasksToRemove[i]].Callback.Invoke(new BatchEvent(tasksToRemove[i]));
                    }
                    catch (Exception)
                    {
                        
                        throw;
                    }
                }
            }
        }

        private void FireTaskCompletedEvent(TaskEvent info)
        {
            foreach (TaskEventHandler handler in TaskEvent.GetInvocationList())
            {
                try
                {
                    handler.Invoke(info);
                }
                catch (Exception)
                {
                }
            }
        }

        public void AddTaskFactory(ITaskFactory task)
        {
            _taskFactories.Add(task.TaskRequestType, task);
        }

        public void AddTaskFactory<T>() where T : ITaskFactory, new()
        {
            AddTaskFactory(new T());
        }

        public void RouteRequest(object requestData)
        {
            Task task = CreateTask(requestData);

            foreach (int batchId in _openBatchScopes)
            {
                _batchedTasks[batchId].OutstandingTaskIds.Add(task.TaskId);
            }

            if (_writeDebugInfo)
            {
                Debug.WriteLine(string.Format((TIME_FORMAT + " TaskDispatcher: Routing {1}. [{2}]"), DateTime.Now, requestData.GetType(), requestData));
            }

            _taskPool.QueueTask(task, _taskPool_TaskCompleted);
        }

        private Task CreateTask(object requestData)
        {
            Task task;
            if (_taskFactories.ContainsKey(requestData.GetType()))
            {
                task = _taskFactories[requestData.GetType()].CreateTask(requestData);
            }
            else
            {
                throw new ArgumentException("The request type is not supported.", "requestData");
            }

            return task;
        }

        public event TaskEventHandler TaskEvent;

        public bool WriteDebugInfo
        {
            get { return _writeDebugInfo; }
            set { _writeDebugInfo = value; }
        }

        private void BatchScopeEnded(int batchId)
        {
            _openBatchScopes.Remove(batchId);
        }

        public BatchScope CreateBatchScope(BatchEventHandler callback)
        {
            BatchScope scope = new BatchScope(BatchScopeEnded);
            _openBatchScopes.Add(scope.BatchId);
            _batchedTasks.Add(scope.BatchId, new BatchScopeInfo(callback));

            return scope;
        }

        private class BatchScopeInfo
        {
            private readonly BatchEventHandler _callback;
            private readonly List<int> _outstandingTaskIds = new List<int>();

            public BatchScopeInfo(BatchEventHandler callback)
            {
                _callback = callback;
            }

            public BatchEventHandler Callback
            {
                get { return _callback; }
            }

            public List<int> OutstandingTaskIds
            {
                get { return _outstandingTaskIds; }
            }
        }

        private readonly List<int> _openBatchScopes = new List<int>();
        private readonly Dictionary<int, BatchScopeInfo> _batchedTasks = new Dictionary<int, BatchScopeInfo>();
    }
}